#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include "LinearSolver.h"

template<class T>
T readCorrectly() {
	T result;
	                                    
	for(;;) {
		std::cin >> result;
		if( !std::cin.fail() ) {
			return result;
		} else {
			std::cout << "Enter correct value" << std::endl;
			std::cin.clear();
			std::cin.sync();
		}
	}

}

void cinCheck () 
{
	if( std::cin.fail() ) {
		std::cout << "Dear user, remove your eyes, and then cut fingers because it is not useful to you." << std::endl;
		std::cout << "YOU MUST ENTER NUMBERS" << std::endl;
		std::cin.clear();
		std::cin.sync();
	}
}

void readVector (Vector& vector)
{
	bool readSuccessfull = false;
	while( !readSuccessfull)
	{
		try
		{
			std::cin >> vector;
			readSuccessfull = true;
		}
		catch(std::invalid_argument& e)
		{	
			std::cout << "Some error occured: " << e.what() << "Try again" << std::endl;
			std::cin.clear();
			std::cin.sync();
		}
	}
}

void readMatrix (Matrix& matrix)
{
	bool readSuccessfull = false;
	while( !readSuccessfull)
	{
		try
		{
			std::cin >> matrix;
			readSuccessfull = true;
		}
		catch(std::invalid_argument& e)
		{	
			std::cout << "Some error occured: " << e.what() << "Try again" << std::endl;
			std::cin.clear();
			std::cin.sync();
		}
	}
}

void vectorAddition() {
	std::cout << "===========================ADDION OF TWO VECTORS===========================" << std::endl;

	std::cout << "Enter dimention of first vector: N = ";
	size_t firstDimension = readCorrectly<size_t>();
	
	Vector firstVector(firstDimension);
	std::cout << "Enter first vector in format: {1, 2, 3, ... N }" << std::endl;
	readVector (firstVector);

	std::cout << "Enter dimention of second vector: N = ";
	size_t secondDimension = readCorrectly<size_t>();
	Vector secondVector(secondDimension);
	std::cout << "Enter second vector in format: {1, 2, 3, ... N }" << std::endl;
	readVector (secondVector);

	std::cout << "RESULT:" << firstVector + secondVector << std::endl;
}

void vectorSubstruction() {
	std::cout << "=======================SUBSTRUCTION VECTOR BY VECTOR========================" << std::endl;

			std::cout << "Enter dimention of first vector: N = ";
			size_t firstDimension = readCorrectly<size_t>();
			Vector firstVector(firstDimension);
			std::cout << "Enter first vector in format: {1, 2, 3, ... N }" << std::endl;
			readVector (firstVector);

			std::cout << "Enter dimention of second vector: N = ";
			size_t secondDimension = readCorrectly<size_t>();
			Vector secondVector(secondDimension);
			std::cout << "Enter second vector in format: {1, 2, 3, ... N }" << std::endl;
			readVector (secondVector);

			std::cout << "RESULT:" << firstVector - secondVector << std::endl;
		
}

void vectorByNumberMultiplition() {
	std::cout << "=======================MULTIPLITION VECTOR BY NUMBER======================" << std::endl;

			std::cout << "Enter dimention of vector: N = ";
			size_t firstDimension = readCorrectly<size_t>();
			Vector firstVector(firstDimension);
			std::cout << "Enter vector in format: {1, 2, 3, ... N }" << std::endl;
			readVector (firstVector);

			std::cout << "Enter number for multiplition: NUMBER = ";
			double number;
			std::cin >> number;
			cinCheck ();
			std::cout << "RESULT:" << firstVector * number << std::endl;
}

void vectorByVectorMultiplition() {
	std::cout << "=======================MULTIPLITION VECTOR BY VECTOR======================" << std::endl;
			std::cout << "Enter dimention of first vector: N = ";
			size_t firstDimension = readCorrectly<size_t>();
			Vector firstVector(firstDimension);
			std::cout << "Enter first vector in format: {1, 2, 3, ... N }" << std::endl;
			readVector (firstVector);

			std::cout << "Enter dimention of second vector: N = ";
			size_t secondDimension = readCorrectly<size_t>();
			Vector secondVector(secondDimension);
			std::cout << "Enter second vector in format: {1, 2, 3, ... N }" << std::endl;
			readVector (secondVector);

			std::cout << "RESULT:" << firstVector * secondVector << std::endl;
}

void matrixDeterminant() {
	std::cout << "==========================SEARCHING FOR DETERMINANT=========================" << std::endl;

			std::cout << "Enter rowsCount of matrix: ROWSCOUNT = ";
			size_t rowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of matrix: COLUMNSCOUNT = ";
			size_t columnsCount = readCorrectly<size_t>();
			std::cout << "Enter matrix in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix matrix(rowsCount, columnsCount);
			readMatrix (matrix);

			std::cout << "RESULT:" << matrix.determinant() << std::endl;
}

void matrixInversing() {
	std::cout << "==============================MATRIX INVERSING=============================" << std::endl;

			std::cout << "Enter rowsCount of matrix: ROWSCOUNT = ";
			size_t rowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of matrix: COLUMNSCOUNT = ";
			size_t columnsCount = readCorrectly<size_t>();
			std::cout << "Enter matrix in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix matrix(rowsCount, columnsCount);
			readMatrix (matrix);
			Matrix inverseMatrix = matrix.inverse ();
			std::cout << "RESULT:" <<  inverseMatrix.roundToZero() << std::endl;
}

void matrixToNumberMultiply() {
	std::cout << "=======================MULTIPLING MATRIX BY NUMBER========================" << std::endl;

			std::cout << "Enter rowsCount of matrix: ROWSCOUNT = ";
			size_t rowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of matrix: COLUMNSCOUNT = ";
			size_t columnsCount = readCorrectly<size_t>();
			std::cout << "Enter matrix in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix matrix(rowsCount, columnsCount);
			readMatrix (matrix);

			std::cout << "Enter number for multipling: NUMBER = ";
			double number;
			std::cin >> number;
			cinCheck ();
			std::cout << "RESULT:" << matrix * number << std::endl;
}

void matrixByVectorMultiply() {
	std::cout << "=======================MULTIPLING MATRIX BY VECTOR========================" << std::endl;

			std::cout << "Enter rowsCount of matrix: ROWSCOUNT = ";
			size_t rowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of matrix: COLUMNSCOUNT = ";
			size_t columnsCount = readCorrectly<size_t>();
			std::cout << "Enter matrix in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix matrix(rowsCount, columnsCount);
			readMatrix (matrix);

			std::cout << "Enter dimention of vector: N = ";
			size_t firstDimension = readCorrectly<size_t>();
			Vector vector(firstDimension);
			std::cout << "Enter vector in format: {1, 2, 3, ... N }" << std::endl;
			readVector (vector);

			std::cout << "RESULT:" << matrix * vector << std::endl;
}

void matrixByMatrixMultiply() {
	std::cout << "=======================MULTIPLING MATRIX BY MATRIX========================" << std::endl;

			std::cout << "Enter rowsCount of first matrix: ROWSCOUNT = ";
			size_t firstRowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of first matrix: COLUMNSCOUNT = ";
			size_t firstColumnsCount = readCorrectly<size_t>();
			std::cout << "Enter first matrix in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix firstMatrix(firstRowsCount, firstColumnsCount);
			readMatrix (firstMatrix);

			std::cout << "Enter rowsCount of second matrix: ROWSCOUNT = ";
			size_t secondRowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of second matrix: COLUMNSCOUNT = ";
			size_t secondColumnsCount = readCorrectly<size_t>();
			std::cout << "Enter second matrix in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix secondMatrix(secondRowsCount, secondColumnsCount);
			readMatrix (secondMatrix);

			std::cout << "RESULT:" << firstMatrix * secondMatrix << std::endl;
}

void workWithVectors() {
	std::cout << "=============================VECTOR CALCULATOR=============================" << std::endl;
	std::cout << "Enter 1 - to add two vectors, 2 - to subtract two vectors"					   << std::endl;
	std::cout << "3 - to multiplied vector by vector, 4 - to multiplied vector by number"      << std::endl;
	std::cout << "0 - to exit"																   << std::endl;
	std::cout << "===========================================================================" << std::endl;

	int workWithVectorsMode = readCorrectly<int>();
	

	switch( workWithVectorsMode )
	{
	case 0:
		return;
		break;
	case 1:
		vectorAddition();
		break;

	case 2:
		vectorSubstruction();			
		break;

	case 3:
		vectorByNumberMultiplition();
		break;

	case 4:
		vectorByVectorMultiplition();
		break;

	default:
		throw std::logic_error("MESSAGE: Incorrect value. Enter value in range [0, 4]");
		break;
	}
}

void workWithMatrixs () {
	std::cout << "=============================MATRIX CALCULATOR=============================" << std::endl;
	std::cout << "Enter 1 - to find the determinant, 2 - for matrix invetsing "				   << std::endl;
	std::cout << "3 - to multiplied matrix by number, 4 - to multiplied matrix by vector"      << std::endl;
	std::cout << "5 - to multiplied matrix by matrix"										   << std::endl;
	std::cout << "0 - to exit"																   << std::endl;
	std::cout << "===========================================================================" << std::endl;

	int workWithMatrixsMode;
	std::cin >> workWithMatrixsMode;
	cinCheck ();
	switch( workWithMatrixsMode )
	{

	case 0:
		return;
		break;
	case 1:
		matrixDeterminant();
		break;

	case 2:
		matrixInversing();
		break;


	case 3:
		matrixToNumberMultiply();
		break;

	case 4:
		matrixByVectorMultiply();
		break;

	case 5:
		matrixByMatrixMultiply();
		break;
	default:
		throw std::logic_error("MESSAGE: Incorrect value. Enter value in range [0, 5]");
		break;	
	}
}

void workWithEqules() {
	std::cout << "============================EQUATION CALCULATOR============================" << std::endl;
	std::cout << "Enter 1 - to solve equations"												   << std::endl;
	std::cout << "0 - to exit"																   << std::endl;
	std::cout << "===========================================================================" << std::endl;

	int workWithVectorsMode;
	std::cin >> workWithVectorsMode;
	cinCheck ();


	switch( workWithVectorsMode )
	{
	case 0:
		return;
		break;
	case 1:
		{	
			std::cout << "===========================SOLUTION OF THE EQUATIONS===========================" << std::endl;

			std::cout << "Enter rowsCount of matrix argument: ROWSCOUNT = ";
			size_t rowsCount = readCorrectly<size_t>();
			std::cout << "Enter columnsCount of matrix argument: COLUMNSCOUNT = ";
			size_t columnsCount = readCorrectly<size_t>();
			std::cout << "Enter matrix argument in format: {{1, 2, 3,..., N}, {} ...,{..., N}}" << std::endl;
			Matrix matrix(rowsCount, columnsCount);
			readMatrix (matrix);

			std::cout << "Enter dimention of vector of values: N = ";
			size_t firstDimension = readCorrectly<size_t>();
			Vector vector(firstDimension);
			std::cout << "Enter vector of values in format: {1, 2, 3, ... N }" << std::endl;
			readVector (vector);

			std::cout << "RESULT:" <<LinearSolver::solve( matrix, vector) << std::endl;
		}
		break;
	default:
		throw std::logic_error("MESSAGE: Incorrect value. Enter value in range [0, 2]");
		break;
	}
}